#pragma once
// 这个是用来实现线程池的！ 
// 而且，将使用过往封装的组件来进行使用！
#include <cstdio>
#include <iostream>
#include <vector>
#include <functional>
#include <queue>
#include <string>
#include "Thread.hpp"
#include "Log.hpp"
#include "Cond.hpp"
#include "Mutex.hpp"

//单例模式

namespace myThreadPool{

    using namespace myThread;
    using namespace myMutex;
    using namespace myCond;
    using namespace myLog;

    const int _default_thread_num = 5;
    
    

    // 本质上就是在写生产者消费者模型罢了，所以，生产的放队列，具体生产的产品类型就为Queue_T

    
    template<class Queue_T>
    class ThreadPool{
    private:
    // 从任务队列中拿任务来做！
        void ThreadHandler(){

            // 但是，这里的问题是，如果说当前设置了线程池终止怎么办呢？
            // 这时候就需要每个线程都退出。
            // 但是，这里还需要注意，如果说：
            // 队列中还有任务是要处理完才能终止(终止一个线程，所有线程终止！)

            //这里的Stop()接口只是简单地把状态给设置了，所以，需要在ThreadHandler()这里来控制线程是否退出
            //而且，这里需要等待在条件变量下，后序还要想办法唤醒这些线程

            //而且，在设置了线程池终止的时候，是有如下结果情况的：
            //1.所有线程可能在等待
            //2.所有线程正在运行
            //3.部分等待，部分运行
            //4.队列中还有任务！
            
            

            while(1){
                //只能从系统中拿到名字！
                char name[128];
                pthread_getname_np(pthread_self(), name, sizeof(name));

                Queue_T task;  
                //从这里开始要拿任务
                {
                    Lock_Guard guard(_queue_lock);
                    // 可能队列为空
                   /*  while(_task_queue.empty()){
                        //队列为空
                        ++_wait_num;
                        _queue_empty.Wait(_queue_lock);
                        --_wait_num;
                    } */

                    //这里修改以下逻辑

                    // 但是，下面已经判断了_task_queue.empty()条件，上面怎么半？
                    // 我们现在需要思考，什么时候，线程才需要去阻塞等待？
                    // 1. 任务队列为空 
                    // 2. 线程池状态为运行
                    // 使用while判断是防止伪唤醒
                    while(_task_queue.empty() && _is_running){
                        ++_wait_num;
                        _queue_empty.Wait(_queue_lock);
                        --_wait_num;
                    }


                    // 只有运行状态是停止，并且队列为空的时候，需要退出！
                    if(_task_queue.empty() && !_is_running){
                        LOG(LogLevel::INFO) << "当前任务队列为空, 且线程池运行状态为终止, 当前线程结束调用!";
                        break;
                    }


                    // 这个时候就说明队列中有任务
                    task = _task_queue.front();
                    _task_queue.pop();
                }
                task();
                sleep(1); 

                // 通过上面的写法，达成了:
                // 如果设置了Stop：
                // 1.睡眠的线程会被全部唤醒！
                // 2.而一旦唤醒并且设置_is_running = false
                // 那么就不会进入到等待环节的代码了！
                // 转而判断任务队列是否还有任务！
                // 如果还有，该线程没有办法退出！ //那就只能继续往下执行任务
                // 又因为每个线程在中间部分是加锁的，所以线程是一个个判断任务队列的。
                // 直到判断到任务队列为空了！这个时候就线程所有的都退出了！(每个线程任务执行完毕)
            }
        }



        // 懒汉模式的最大特点就是:
        // 不能让外界创建对象，只能使用提供的单例！
        // 即使用static 对象的指针，提前把对应的指针类型保存在全局区域
        // 然后直到使用的时候，在类内进行创建！


        // 构造函数是一定要有的！
        ThreadPool(int thread_num = _default_thread_num)
            :_thread_num(thread_num),
            _is_running(false),
            _wait_num(0)
        {
            // 直接让线程池类对象创建的时候就把所有的线程类对象准备好
            for(int i = 0; i < thread_num; ++i){
                _threads.emplace_back([this](){
                    ThreadHandler();
                });
            }
            LOG(LogLevel::INFO) << "线程池被创建!";
        }

        //拷贝构造、赋值重载通通删掉
        ThreadPool(const ThreadPool& threadpool) = delete;
        ThreadPool<Queue_T>& operator=(const ThreadPool& threadpool) = delete;

        //这里放在公有私有都可以。但是放在私有就好了。
        //获取单例的时候自动就启动
        void Start(){
            if(_is_running)
                LOG(LogLevel::WARNING) << "线程池已经启动" ;
            else LOG(LogLevel::DEBUG) << "线程池启动" ;
            _is_running = true;
            // 剩下的就是依次启动_threads中的线程
            for(auto& thread : _threads){
                thread.Start();
            }
        }

        // 这里唤醒线程的时候，很可能也会多个线程进行访问这个_wait_num;
        void WaitAllThread(){
            Lock_Guard guard(_queue_lock);
            if(_wait_num) {
                LOG(LogLevel::DEBUG) << "唤醒所有线程" ;
                _queue_empty.Broadcast();
            }
        }

        void WaitOneThread(){
            Lock_Guard guard(_queue_lock);
            if(_wait_num) {
                LOG(LogLevel::DEBUG) << "唤醒一个线程" ;
                _queue_empty.Signal();
            }
        }



    public:
        
        //问题1 如果想要通过这个函数让外界获得单例的指针，需要一个对象！
        //但是对象又不能自主创建！ 所以，这个函数只能是static函数，可以直接通过类域访问

        //问题2，对指针判空的逻辑不加锁会出问题！很可能导致多个执行流重入！导致创建多个对象

        //问题3，不能使用类内的锁！因为对象还没有创建！所以还需要一个静态的锁！

        //问题4，但是，问题2的情况只可能出现一次，后序就不会出现了，如果直接加锁会影响效率

        //1.static
        static ThreadPool<Queue_T>* GetInstance(){
            // 4. 后序情况，肯定不为空，就不进来
            if(_pool == nullptr){
                 //2.加锁
                Lock_Guard guard(_ins_lock); //3._ins_lock

                // 4. 但是，不确定是否有问题，所以再来一次
                if(_pool == nullptr){
                    LOG(LogLevel::INFO) << "第一次获取单例，需要进行创建，并启动线程池";
                    _pool = new ThreadPool<Queue_T>();
                    _pool->Start();
                }
            }
            return _pool;
        }
        

        void Stop(){
            // 终止这个线程池
            _is_running = false;
            // 但是，这里有一个问题就是：如果说当前的队列中还有任务怎么办？
            // 这个时候就得结合线程执行的函数逻辑综合考量！

            //这里必须把所有的等待的线程唤醒！否则无法退出！
            WaitAllThread();
            LOG(LogLevel::DEBUG) << "唤醒所有的线程, 准备终止线程池";
        }

        void Join(){
            // 这里千万不要判断线程池没启动就不回收 ，会出问题
            for(int i = 0; i < _threads.size(); ++i){
                _threads[i].Join();
            }
        }

        bool Enqueue(const Queue_T& in){
            // 只有处于运行状态才能入队，否则上面的退出逻辑是错的！
            if(_is_running){
                _task_queue.push(in);
                //每生产一个任务到队列中，就需要唤醒一个！要不然有很多都在睡眠
                WaitOneThread();
                return true;
            }
            return false;
        }


        ~ThreadPool(){}

    private:
        int _thread_num;
        std::vector<Thread> _threads;
        // 这一次实现的时候，就不再设置消费场所的容量上限！所以，就不用对消费者端进行阻塞等待！
        std::queue<Queue_T> _task_queue;
        Mutex _queue_lock;
        Cond _queue_empty;

        bool _is_running;
        int _wait_num;

        static ThreadPool* _pool;
        static Mutex _ins_lock;
    };

    template<class Queue_T>
    ThreadPool<Queue_T>* ThreadPool<Queue_T>::_pool = nullptr;

    template<class Queue_T>
    Mutex ThreadPool<Queue_T>::_ins_lock;
    //静态成员需要类外初始化

    // 这里采用一次性创建若干线程的方式，没有消费的内容就进行阻塞等待
}